#include <QHostAddress>
#include <QSslConfiguration>
#include <QFileDialog>
#include <QThread>
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "datadefine.h"

// 每个线程每次发送4M：4*1024*1024
#define SEND_BLOCK_SIZE 4194304


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    clntNum = 0;
    useSSL = true;
    tcpSocket = nullptr;
    tranThreads = nullptr;
    tranWorks = nullptr;
    fileInfo.sentByte = 0;
    ui->setupUi(this);

    QSslConfiguration sslConf = QSslConfiguration::defaultConfiguration();
    sslConf.setPeerVerifyMode(QSslSocket::VerifyNone);
    QSslConfiguration::setDefaultConfiguration(sslConf);

    setWindowTitle("文件传输");
    setFixedSize(470, 178);

    ui->checkBoxSsl->setCheckState(Qt::CheckState::Checked);
    ui->pushButtonConnect->setEnabled(true);
    ui->pushButtonClose->setEnabled(false);
    ui->pushButtonSend->setEnabled(false);
    ui->checkBoxSsl->setEnabled(true);
    ui->pushButtonPath->setEnabled(false);
    ui->statusBar->setSizeGripEnabled(false);

    pkgBuffer.Initialize(2048);
    assert(nullptr != pkgBuffer.pBuffer);
}

MainWindow::~MainWindow()
{
    if (nullptr != tranThreads)
    {
        emit(stop());

        for (int16_t indx = 0; indx < clntNum; indx++)
        {
            tranThreads[indx].quit();
            tranThreads[indx].wait();
        }

        delete [] tranThreads;
        tranThreads = nullptr;
    }

    if (nullptr != tranWorks)
    {
        delete [] tranWorks;
        tranWorks = nullptr;
    }

    if (fileInfo.file.isOpen())
        fileInfo.file.close();

    pkgBuffer.Destroy();
    delete ui;
}


void MainWindow::on_pushButtonConnect_clicked()
{
    //获取服务器ip和端口
    QString ip = ui->lineEditIp->text();
    qint16 port = ui->lineEditPort->text().toInt();
    bool bSsl = (Qt::CheckState::Checked == ui->checkBoxSsl->checkState());

    if (nullptr == tcpSocket)
        delete tcpSocket;

    //分配空间，指定父对象
    if (bSsl)
        tcpSocket = new QSslSocket(this);
    else
        tcpSocket = new QTcpSocket(this);

    conConnected = connect(tcpSocket, &QTcpSocket::connected, this, &MainWindow::connected);
    conReadyRead = connect(tcpSocket, &QTcpSocket::readyRead, this, &MainWindow::readyRead);
    conDisconnected = connect(tcpSocket, &QTcpSocket::disconnected, this, &MainWindow::disconnected);

    if (bSsl)
    {
        conEncrypted = connect((QSslSocket*)tcpSocket, &QSslSocket::encrypted, this, &MainWindow::encrypted);
        conPeerVerifyError = connect((QSslSocket*)tcpSocket, &QSslSocket::peerVerifyError, this, &MainWindow::peerVerifyError);
        conModeChanged = connect((QSslSocket*)tcpSocket, &QSslSocket::modeChanged, this, &MainWindow::modeChanged);
    }

    //主动和服务器建立连接
    if (bSsl)
        ((QSslSocket*)tcpSocket)->connectToHostEncrypted(ip, port);
    else
        tcpSocket->connectToHost(QHostAddress(ip), port);

    useSSL= bSsl;
    svrIp = ip;
    svrPort = port;
}

void MainWindow::on_pushButtonClose_clicked()
{
    if (nullptr != tranThreads)
    {
        emit(stop());

        for (int16_t indx = 0; indx < clntNum; indx++)
        {
            tranThreads[indx].quit();
            tranThreads[indx].wait();
        }

        delete [] tranThreads;
        tranThreads = nullptr;
    }

    if (nullptr != tranWorks)
    {
        delete [] tranWorks;
        tranWorks = nullptr;
    }

    //主动和对方断开连接
    tcpSocket->disconnectFromHost();
    tcpSocket->close();

    if (fileInfo.file.isOpen())
        fileInfo.file.close();
}

void MainWindow::on_pushButtonPath_clicked()
{
    QString filePath = QFileDialog::getOpenFileName(this, "选择传输的文件", "E:", "所有文件(*.*);;");
    ui->lineEditPath->setText(filePath);
}

void MainWindow::on_pushButtonSend_clicked()
{
    QString strPath = ui->lineEditPath->text();

    if (strPath.isEmpty())
    {
        ui->statusBar->showMessage("错误：文件路径为空");
        return;
    }

    QFileInfo fi(strPath);

    if (!fi.isFile())
    {
        ui->statusBar->showMessage("选择的路径不是文件");
        return;
    }

    fileInfo.file.setFileName(strPath);

    if (!fileInfo.file.exists())
    {
        ui->statusBar->showMessage("文件不存在");
        return;
    }

    fileInfo.fileName = fi.fileName();
    fileInfo.fileSize = fi.size();

    timeStart = (double)clock();
    fileUploadRequest();
}

void MainWindow::connected()
{
    qDebug() << "QTcpSocket::connected";
    ui->statusBar->showMessage("成功和服务器建立连接");
    ui->pushButtonConnect->setEnabled(false);
    ui->pushButtonClose->setEnabled(true);
    ui->pushButtonSend->setEnabled(true);
    ui->checkBoxSsl->setEnabled(false);
    ui->pushButtonPath->setEnabled(true);
    focusNextChild();

    if (!useSSL)
    {
        qDebug() << "QSslSocket::encrypted";
        protocol prot{0xA5C3, 13, "IodpModule", 0, 0x5A3C};
        qint64 sent = tcpSocket->write((char*)&prot, sizeof(prot));
        qDebug() << "tcpSocket->write:" << sent;
    }
}

void MainWindow::disconnected()
{
    ui->statusBar->showMessage("已和服务器断开连接");
    ui->pushButtonConnect->setEnabled(true);
    ui->pushButtonClose->setEnabled(false);
    ui->pushButtonSend->setEnabled(false);
    ui->checkBoxSsl->setEnabled(true);
    ui->pushButtonPath->setEnabled(false);
    focusNextChild();

    disconnect(conConnected);
    disconnect(conReadyRead);
    disconnect(conDisconnected);

    if (useSSL)
    {
        disconnect(conEncrypted);
        disconnect(conPeerVerifyError);
        disconnect(conModeChanged);
    }
}

void MainWindow::encrypted()
{
    qDebug() << "QSslSocket::encrypted";
    protocol prot{0xA5C3, 13, "IodpModule", 0, 0x5A3C};
    qint64 sent = tcpSocket->write((char*)&prot, sizeof(prot));
    qDebug() << "tcpSocket->write:" << sent;
}

void MainWindow::peerVerifyError(const QSslError &error)
{
    qDebug() << "QSslSocket::peerVerifyError = " << error;
}

void MainWindow::modeChanged(QSslSocket::SslMode newMode)
{
    qDebug() << "QSslSocket::modeChanged = " << newMode;
}

bool MainWindow::isFileExist(QString filePath)
{
    QFileInfo fileInfo(filePath);

    if(fileInfo.isFile())
        return true;

    return false;
}

void MainWindow::readyRead()
{
    bool wholePkg = false;
    int64_t readByte = 0;

    if (0 == pkgBuffer.nPkgLen)
    {
        readByte = tcpSocket->read((char*)pkgBuffer.WritePos(), sizeof(uint16_t) - pkgBuffer.nCurLen);
        pkgBuffer.nCurLen += readByte;

        if (pkgBuffer.nCurLen >= sizeof(uint16_t))
        {
            pkgBuffer.nPkgLen = *(uint16_t*)pkgBuffer.pBuffer;
            pkgBuffer.nCurLen = 0;
        }
    }

    if (0 < pkgBuffer.nPkgLen)
    {
        uint16_t needByte = pkgBuffer.nPkgLen - pkgBuffer.nCurLen;
        readByte = tcpSocket->read((char*)pkgBuffer.WritePos(), needByte);

        if (readByte >= needByte)
            wholePkg = true;
    }

    if (wholePkg)
    {
        packageProcess();
    }
}

bool MainWindow::packageProcess()
{
    bool result = false;
    uint8_t *ptr = pkgBuffer.pBuffer;
    DataCmd cmd = *(DataCmd*)ptr;

    ptr += sizeof(DataCmd);
    uint16_t len = pkgBuffer.nPkgLen - sizeof(DataCmd);

    switch (cmd)
    {
    case DataCmd::HandShake:
    {
        if (*(bool*)ptr)
            ui->statusBar->showMessage("握手成功");
        result = true;
        break;
    }

    case DataCmd::Module:
        break;

    case DataCmd::FileUpload:
        result = fileUploadResponse(ptr, len);
        break;

    default:
        break;
    }

    pkgBuffer.Reset();

    return result;
}

bool MainWindow::fileUploadRequest()
{
    uint16_t pkgSize = 0;
    uint8_t *ptr = pkgBuffer.pBuffer;

    // 请求包类型
    ptr += sizeof(uint16_t);
    *(DataCmd *)ptr = DataCmd::FileUpload;
    pkgSize += sizeof(DataCmd);

    // 请求使用多少个客户端上传数据
    int coreNumber = QThread::idealThreadCount();
    ptr += sizeof(DataCmd);
    *(uint16_t*)ptr = (uint16_t)coreNumber;
    pkgSize += sizeof(uint16_t);

    // 文件长度
    ptr += sizeof(uint16_t);
    *(int64_t *)ptr =  fileInfo.fileSize;
    pkgSize += sizeof(int64_t);

    // 文件名
    QByteArray arrName = fileInfo.fileName.toUtf8();
    int nameLen = arrName.length();
    ptr += sizeof(int64_t);
    memcpy(ptr, arrName.data(), nameLen);
    pkgSize += nameLen;

    // 字符串结束符
    ptr += nameLen;
    *ptr = 0;
    pkgSize += 1;

    // 包数据长度
    *(uint16_t*)pkgBuffer.pBuffer = pkgSize;
    pkgSize += sizeof(uint16_t);

    qint64 sent = 0;
    pkgBuffer.nCurLen = 0;

    do
    {
        sent = tcpSocket->write((char*)pkgBuffer.pBuffer+pkgBuffer.nCurLen, pkgSize);
        pkgBuffer.nCurLen += sent;
        pkgSize -= sent;
        qDebug() << "fileUploadRequest sent =" << sent;
    } while (0 < pkgSize);

    pkgBuffer.Reset();

    return true;
}

bool MainWindow::fileUploadResponse(uint8_t *data, uint16_t size)
{
    uint8_t *ptr = data;
    bool reslut = *(bool*)ptr;
    ptr += sizeof(bool);

    if (!reslut)
        return false;

    clntNum = *(uint16_t*)ptr;
    ptr += sizeof(uint16_t);
    qDebug() << "client number:" << clntNum;
    runThread = clntNum;
    ui->labelThreadNum->setText(QString::number(clntNum));
    QString strMsg = "文件大小：";
    strMsg += QString::number(fileInfo.fileSize);
    strMsg += "字节，上传中……";
    ui->statusBar->showMessage(strMsg);

    uint64_t uploadFlag = *(uint64_t*)ptr;
    ptr += sizeof(void*);
    qDebug("svrFlag:%0x", uploadFlag);

    if (nullptr != tranThreads)
    {
        emit(stop());

        for (int16_t indx = 0; indx < clntNum; indx++)
        {
            tranThreads[indx].quit();
            tranThreads[indx].wait();
        }

        delete [] tranThreads;
        tranThreads = nullptr;
    }

    if (nullptr != tranWorks)
    {
        delete [] tranWorks;
        tranWorks = nullptr;
    }

    if (!fileInfo.file.open(QIODevice::ReadOnly))
        return false;

    tranThreads = new QThread[clntNum];

    if (nullptr == tranThreads)
        return false;

    tranWorks = new TransferWorker[clntNum];

    if (nullptr == tranWorks)
        return false;

    QThread *curThread = nullptr;
    TransferWorker *curWorker = nullptr;

    for (int16_t indx = 0; indx < clntNum; indx++)
    {
        curThread = &tranThreads[indx];
        curWorker = &tranWorks[indx];

        curWorker->setIndex(indx);
        curWorker->setUploadFlag(uploadFlag);
        curWorker->setUseSsl(useSSL);
        curWorker->setServerIp(svrIp);
        curWorker->setServerPort(svrPort);
        curWorker->setFile(&fileInfo.file);
        curWorker->setLock(&fileLock);

        connect(this, &MainWindow::stop, curWorker, &TransferWorker::stop);
        connect(this, &MainWindow::sendMsg, curWorker, &TransferWorker::receiveMsg);
        connect(curWorker, &TransferWorker::sendMsg, this, &MainWindow::receiveMsg);
        connect(curThread, &QThread::started, curWorker, &TransferWorker::start);
        curWorker->moveToThread(curThread);
        curThread->start();
    }

    fileInfo.sentByte = 0;

    return true;
}

void MainWindow::receiveMsg(quint16 indx, quint16 msg)
{
    uint16_t index = indx;
    uint16_t message = msg;

    if (index >= clntNum)
        return;

//    TransferWorker *curWorker = &tranWorks[indx];

    switch ((WorkerMsg)message)
    {
    case WorkerMsg::GetFileData:
    {
        int64_t sendStart = 0;
        uint32_t sendLen = 0;

        spinLock.lock();

        if (fileInfo.sentByte < fileInfo.fileSize)
        {
            sendStart = fileInfo.sentByte;

            if (fileInfo.sentByte + SEND_BLOCK_SIZE <= fileInfo.fileSize)
            {
                fileInfo.sentByte += SEND_BLOCK_SIZE;
                sendLen = (uint32_t)SEND_BLOCK_SIZE;
            }
            else
            {
                sendLen = fileInfo.fileSize - fileInfo.sentByte;
                fileInfo.sentByte = fileInfo.fileSize;
            }
        }

        spinLock.unlock();
        emit(sendMsg(index, (quint16)WorkerMsg::SendFileData, sendStart, sendLen));
        ui->progressBar->setValue(((float)fileInfo.sentByte/(float)fileInfo.fileSize)*100);

        double timeCurrent = (double)clock();
        float apartTime = ((float)timeCurrent - (float)timeStart)/1000;
        float avgSpeed = (float)fileInfo.fileSize/apartTime/1024/128;
        ui->labelSpeed->setText(QString::number(avgSpeed, 'f', 3));

        break;
    }

    case WorkerMsg::TransferFinish:
    {
        runThread--;
        ui->labelThreadNum->setText(QString::number(runThread));

        if (0 == runThread)
        {
            double timeEnd = (double)clock();
            QString strMsg("上传时间为：");
            float apartTime = ((float)timeEnd - (float)timeStart)/1000;
            strMsg += QString::number(apartTime, 'f', 3);
            float avgSpeed = (float)fileInfo.fileSize/apartTime/1024/128;
            strMsg += "  平均速度：";
            strMsg += QString::number(avgSpeed, 'f', 3);
            strMsg += "Mbps";
            ui->statusBar->showMessage(strMsg);

            if (nullptr != tranThreads)
            {
                emit(stop());

                for (int16_t indx = 0; indx < clntNum; indx++)
                {
                    tranThreads[indx].quit();
                    tranThreads[indx].wait();
                }

                delete [] tranThreads;
                tranThreads = nullptr;
            }

            if (nullptr != tranWorks)
            {
                delete [] tranWorks;
                tranWorks = nullptr;
            }
        }

        // 发送全部传输完成的消息

        break;
    }

    default:
        break;
    }
}
